<?php

/**
 * Created by PhpStorm.
 * User: caobo
 * Date: 2019-02-21
 * Time: 09:17
 */

class waf {

    const ARGS_ARR = [
        'url' => "/\\=\\+\\/v(?:8|9|\\+|\\/)|\\%0acontent\\-(?:id|location|type|transfer\\-encoding)/is",

        'xss' => "/[\\'\\\"\\;\\*\\<\\>].*\\bon[a-zA-Z]{3,15}[\\s\\r\\n\\v\\f]*\\=|\\b(?:expression)\\(|\\<script[\\s\\\\\\/]|\\<\\!\\[cdata\\[|\\b(?:eval|alert|prompt|msgbox)\\s*\\(|url\\((?:\\#|data|javascript)/is",

        'sql' => "/[^\\{\\s]{1}(\\s|\\b)+(?:select\\b|update\\b|insert(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+into\\b).+?(?:from\\b|set\\b)|[^\\{\\s]{1}(\\s|\\b)+(?:create|delete|drop|truncate|rename|desc)(?:(\\/\\*.*?\\*\\/)|(\\s)|(\\+))+(?:table\\b|from\\b|database\\b)|into(?:(\\/\\*.*?\\*\\/)|\\s|\\+)+(?:dump|out)file\\b|\\bsleep\\([\\s]*[\\d]+[\\s]*\\)|benchmark\\(([^\\,]*)\\,([^\\,]*)\\)|(?:declare|set|select)\\b.*@|union\\b.*(?:select|all)\\b|(?:select|update|insert|create|delete|drop|grant|truncate|rename|exec|desc|from|table|database|set|where)\\b.*(charset|ascii|bin|char|uncompress|concat|concat_ws|conv|export_set|hex|instr|left|load_file|locate|mid|sub|substring|oct|reverse|right|unhex)\\(|(?:master\\.\\.sysdatabases|msysaccessobjects|msysqueries|sysmodules|mysql\\.db|sys\\.database_name|information_schema\\.|sysobjects|sp_makewebtask|xp_cmdshell|sp_oamethod|sp_addextendedproc|sp_oacreate|xp_regread|sys\\.dbms_export_extension)/is",

        'other' => "/\\.\\.[\\\\\\/].*\\%00([^0-9a-fA-F]|$)|%00[\\'\\\"\\.]/is",
    ];

    /**
     * @var Request
     */
    private $request;

    /**
     * waf constructor.
     */
    public function __construct(Request $request)
    {
        $this->request = $request;

        $shell = $this->request->Server('SHELL');
        if (!empty($shell)) {
            return;
        }

        /*
        检测请求方式，除了get和post之外拦截下来并写日志。
        */
        $acceptMethod = ['POST', 'GET'];
        $REQUEST_METHOD = $this->request->Server('REQUEST_METHOD');
        if(!in_array($REQUEST_METHOD, $acceptMethod)){
            $this->write_attack_log("method", $REQUEST_METHOD);
        }

        /*
        检测过了则对输入进行简单过滤
        */
        $data = $this->request->addslashes();
        if (!empty($data)) {
            $this->filter_attack_keyword($this->filter_invisible(urldecode($this->filter_0x25($data)))); //对POST的内容进行检测，出现问题拦截并记录
        }
    }

    /*
    检测不可见字符造成的截断和绕过效果，注意网站请求带中文需要简单修改
    */
    function filter_invisible($str){
        for($i=0;$i<strlen($str);$i++){
            $ascii = ord($str[$i]);
            if($ascii>126 || $ascii < 32){ //有中文这里要修改
                if(!in_array($ascii, array(9,10,13))){
                    $this->write_attack_log("interrupt", $str);
                }else{
                    $str = str_replace($ascii, " ", $str);
                }
            }
        }
        $str = str_replace(array("`","|",";",","), " ", $str);
        return $str;
    }

    /*
    检测网站程序存在二次编码绕过漏洞造成的%25绕过，此处是循环将%25替换成%，直至不存在%25
    */
    function filter_0x25($str){
        if(strpos($str,"%25") !== false){
            $str = str_replace("%25", "%", $str);
            return $this->filter_0x25($str);
        }else{
            return $str;
        }
    }

    /*
    攻击关键字检测，此处由于之前将特殊字符替换成空格，即使存在绕过特性也绕不过正则的\b
    */
    function filter_attack_keyword($str){
        if(preg_match(self::ARGS_ARR['url'], $str)){
            $this->write_attack_log("xss", $str);
        }

        if(preg_match(self::ARGS_ARR['sql'], $str)){
            $this->write_attack_log("sqli", $str);
        }

        if(preg_match(self::ARGS_ARR['xss'], $str)){
            $this->write_attack_log("cyberattack", $str);
        }

        $PHP_SELF = $this->request->Server('PHP_SELF');

        //此处文件包含的检测我真的不会写了，求高人指点。。。
        if (!empty($PHP_SELF)) {
            if(substr_count($str,$PHP_SELF) < 2){
                $tmp = str_replace($PHP_SELF, "", $str);
                if(preg_match("/\.\.|.*\.php[35]{0,1}/i", $tmp)){
                    $this->write_attack_log("LFI/LFR", $str);
                }
            }else{
                $this->write_attack_log("LFI/LFR");
            }
        }
        if(preg_match("/base64_decode|eval\(|assert\(/i", $str)){
            $this->write_attack_log("EXEC", $str);
        }
        if(preg_match("/flag/i", $str)){
            $this->write_attack_log("GETFLAG", $str);
        }

    }

    /*
    这里拦截并记录攻击payload
    */
    function write_attack_log($alert, $str = ''){
        $logname = 'cyberattack-' . date('Y-m-d') . '.log';
        $data = date("Y/m/d H:i:s")." -- [{$alert}]" . PHP_EOL . $str . $this->request->get_http_raw().PHP_EOL.PHP_EOL;

        file_put_contents(RES_PATH . 'log/' . $logname, $data, FILE_APPEND);
        if($alert == 'GETFLAG'){
            //echo "HCTF{aaaa}"; //如果请求带有flag关键字，显示假的flag。（2333333）
        }else{
            //echo $alert . $str;
            //sleep(15); //拦截前延时15秒
        }
        throw new Exception("has attack!");
    }
}
